# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 1997,2004 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 

# @(#)59   1.20	 src/rsct/pgs/cmds/hagsreap.pl, gsdaemon, rsct_rzauh, rzauh0431a 3/6/03 10:24:16

#######################################################################
#  hagsreap - cleanup hags core and log files 
#
#  hagsreap DaemonName Partition (Erase|(P|p)[rint]) [ SizeLimit [ NodeNumber [ LogDir [ CoreDir ] ] ] ]
#
#  DaemonName and Partition are required, and specify which daemon to clean up after.
#  SizeLimit defaults to 5 MB, and is the cap in bytes.
#  If NodeNumber is not specified, use HA_DOMAIN_TYPE to determine how to get it.
#  LogDir defaults to /var/ha/log, and is the directory where the log files are kept and cleaned up.
#  CoreDir defaults to LogDir if it's specified,
#                   or /var/ha/run/hags.Partition otherwise
#
#  exit value is the number of (core and log) files removed
#  (note that therefore the "error" return value is 0!)
#######################################################################

print "$0 input arguments: ", join(' ',@ARGV), "\n";

unshift(@INC, '/usr/sbin/rsct/bin');

#require "getopts.pl";

($dir,$progname) = $0 =~ /(.*\/)?(.*)/; # get basename of $0

sub Usage {
    warn "Usage: $progname [ -d DefaultLogName ] DaemonName Partition (Erase|(P|p)[rint]) [ SizeLimit [ NodeNumber [ LogDir [ CoreDir ] ] ] ]\n";
    exit -1;
}

# Avoid getopts, to remove dependencies on Perl libraries...
#if ( 1 != &Getopts( "d:" ) ) {
#    &Usage;
#}
#$DefaultLogName = $opt_d;

if ("-d" eq @ARGV[0]) {
    shift @ARGV;
    $DefaultLogName = shift @ARGV;
}

#
# set locale to "C" so that "ls" output should be locale-free
$ENV{'LC_ALL'} = "C";

($Daemon_Name, $Partition, $EraseOrPrint, $SizeLimit, $Node_Number, $LogDir, $CoreDir) = @ARGV;

$EraseTheFiles = 0; # Operational flag for Erase/Print parameter

if ("" eq $EraseOrPrint) {
	warn "Not enough arguments";
	&Usage;
} else {
	if ("Erase" eq $EraseOrPrint) { #OK!
		$EraseTheFiles = 1;
	} else {
		if ($EraseOrPrint =~ /^[Pp](.*)/) {
			$rest = $1;
			if ( ("" ne $rest) && (index("rint", $rest) != $[) ) {
				warn "Print flag invalid";
				&Usage;
			}
		} else {
			warn "Erase|Print flag invalid";
			&Usage;
		}
	}
}

if ("" eq $SizeLimit) {
	$SizeLimit = 5 * 1024 * 1024; # 5 MB default size limit
} else {
	if ($SizeLimit !~ /^[0-9]+$/) {
		warn "SizeLimit must be numeric";
		&Usage;
	}
}
if ("" eq $Node_Number) {
    if (!defined( $ENV{'HA_DOMAIN_TYPE'} )) {
        die "Node number not given, and HA_DOMAIN_TYPE not set.  Exit.\n";
    } else {
        $domainType = $ENV{'HA_DOMAIN_TYPE'};
        if ($domainType eq "HAES") {
            if (!defined( $ENV{'HB_SERVER_SOCKET'} ) ) {
                $ENV{'HB_SERVER_SOCKET'} = "/var/ha/soc/topsvcs/server_socket";
            }
            $Node_Number=`hats_node_number -d $Partition`;
            while (0 != $?) {
                sleep(2);
                $Node_Number=`hats_node_number -d $Partition`;
            }
	} elsif($domainType eq "CLUSTER") {
	    if(!defined($ENV{'HB_SERVER_SOCKET'})) {
		warn "HB_SERVER_SOCKET is not defined";
        	&Usage;
	    }
            $Node_Number=`hats_node_number -d $Partition`;
            while (0 != $?) {
                sleep(2);
                $Node_Number=`hats_node_number -d $Partition`;
            }
        } else {
            $Node_Number=`/usr/lpp/ssp/install/bin/node_number`;
        }
        chop($Node_Number);
    }
}
if ($Node_Number !~ /^[0-9]+$/) {
	warn "NodeNumber must be numeric";
	&Usage;
}

if ("" eq $LogDir) {
	if($domainType eq "CLUSTER") {
                warn "LogDir is not provided";
                &Usage;
	}
	$LogDir = "/var/ha/log";
	$CoreDir = "/var/ha/run/$Daemon_Name.$Partition";
}
if ("" eq $CoreDir) {
        if($domainType eq "CLUSTER") {
                warn "CoreDir is not provided";
                &Usage;
        }
	$CoreDir = $LogDir;
}

$flag = (1 == $EraseTheFiles) ? "Erase" : "Print";

print "$progname parsed arguments: $Daemon_Name $Partition $flag $SizeLimit $Node_Number $LogDir $CoreDir\n";

sub piperc {
	local($rc) = (256 <= $_[0]) ? $_[0]/256 : $_[0];
	local($cmd) = $_[1];
	local($line) = $_[2];

	if ( 0 != $rc ) {
		warn "\n$!\n";
		warn "$0: $cmd at line $line failed: rc = $rc.\n";
		exit 0;
	}
}

$NaN = 65535;  # Not a Number

# Get all core and log files
# Determine and save their Incarnation Number, name and size

$cmd = "ls -lL $CoreDir |";
if ( ! open(FILES, $cmd ) ) {
	$rc = $! + 0;
	warn "\n$!\n";
}
if (0 == $Node_Number) {
	$Daemon_Name = "$Daemon_Name.$Partition";
}
#process core files
$lastModifiedTime = 0;
while(<FILES>) {
    ($perms, $links, $owner, $group, $size, $month, $day, $time, $name) = split(' ');
    $name =~ s/.*\///o; # get basename of file name; strip off /s and dirname
    if( "" ne $name ){
        @a = stat("$CoreDir/$name");
        $modifiedTime = $a[9];
        if( $lastModifiedTime < $modifiedTime ){
            if($lastModifiedTime > 0){
                unlink "$CoreDir/$lastCoreFile";
            }
            $lastModifiedTime = $modifiedTime;
            $lastCoreFile=$name;
            $CoreSavedFileName = $name;
        } elsif( $modifiedTime < $lastModifiedTime ){
                unlink "$CoreDir/$name";
        }
    }
}
close FILES; # this causes a wait on pipe child process, with $? set to its rc
&piperc( $?, $cmd, __LINE__ );

# collect log files
$cmd = "ls -lL $LogDir |";
# print "$cmd\n";
if ( ! open(FILES, $cmd ) ) {
	$rc = $! + 0;
	warn "\n$!\n";
}
$LogPattern = "$Daemon_Name"."_$Node_Number"."_([0-9]+)\\.$Partition";
$LongLogPattern = "$Daemon_Name"."_$Node_Number"."_([0-9]+)\\.$Partition\.long";
$BadLogPattern = "$Daemon_Name"."_([0-9]+)"."_([0-9]+)\\.$Partition";
$preSaveIncar = $NaN;
$saveIncar = $NaN;
$afterIncar = $NaN;
$saveLogIncar = $NaN;
$numberOfSaveIncar = 0;
$preTime = 0;
$afterTime = 0;
$count = 0;
while(<FILES>) {
    ($perms, $links, $owner, $group, $size, $month, $day, $time, $name) = split(' ');
    $name =~ s/.*\///o; # get basename of file name; strip off /s and dirname
    if ( ($name =~ /^$LogPattern(\.bak)?$/o) && ($NaN > $1) ) {
        $incarnationNumber = $1;
        $bak = $2;
        if ("" eq $bak) { # a regular log file
            # print "$name, $incarnationNumber\n";
            $LogFileName{$incarnationNumber} = $name;
            $LogFileSize{$incarnationNumber} = $size;
            @a = stat("$LogDir/$LogFileName{$incarnationNumber}");
            $modifiedTime = $a[9];
            $LogFileTime{$incarnationNumber} = $modifiedTime;
            # find the incarnation number to save
            if( $count == 0 ){
                if( $modifiedTime ==$lastModifiedTime ){
                    $saveLogIncar = $incarnationNumber;
                    $numberOfSaveIncar = 1;
                } else {
                    $preSaveIncar = $incarnationNumber;
                    $preTime = $LogFileTime{$incarnationNumber};
                }
            } elsif( $count == 1 ){
                if( $modifiedTime ==$lastModifiedTime ){
                    $saveLogIncar = $incarnationNumber;
                    $numberOfSaveIncar = 1;
                } else {
                    $saveIncar = $incarnationNumber;
                    $saveTime = $LogFileTime{$incarnationNumber};
                }
            } elsif( $count == 2 ){
                if( $modifiedTime ==$lastModifiedTime ){
                    $saveLogIncar = $incarnationNumber;
                    $numberOfSaveIncar = 1;
                } else {
                    $afterSaveIncar = $incarnationNumber;
                    $afterTime = $LogFileTime{$incarnationNumber};
                }
            } else{
                if( $modifiedTime ==$lastModifiedTime ){
                    $saveLogIncar = $incarnationNumber;
                    $numberOfSaveIncar = 1;
                } elsif( $preTime < $lastModifiedTime && $afterTime > $lastModifiedTime){
                    $saveLogIncar = $saveIncar;
                    $numberOfSaveIncar = 3;
                } else {
                    if($numberOfSaveIncar == 0 ){
                        $preSaveIncar = $saveIncar;
                        $saveIncar = $afterSaveIncar;
                        $afterSaveIncar = $incarnationNumber;
                        $preTime = $saveTime;
                        $saveTime = $afterTime;
                        $afterTime = $LogFileTime{$incarnationNumber};
                    }
                } 
            }
            if( $modifiedTime ==$lastModifiedTime ){
                $saveLogIncar = $incarnationNumber;
                $numberOfSaveIncar = 1;
            } elsif( $preTime < $lastModifiedTime && $afterTime > $lastModifiedTime){
                $saveLogIncar = $saveIncar;
                $numberOfSaveIncar = 3;
            }
            $count++;
        } else { # a log.bak file
            # print "$name, $incarnationNumber\n";
            $BakFileName{$incarnationNumber} = $name;
            $BakFileSize{$incarnationNumber} = $size;
        }
    } elsif ( ($name =~ /^$LongLogPattern(\.bak)?$/o) && ($NaN > $1) ) {
        $incarnationNumber = $1;
        $bak = $2;
        if ("" eq $bak) { # a regular long log file
            # print "$name, $incarnationNumber\n";
            $LongLogFileName{$incarnationNumber} = $name;
            $LogFileSize{$incarnationNumber} = $size;
        } else { # a long log.bak file
            # print "$name, $incarnationNumber\n";
            $LongBakFileName{$incarnationNumber} = $name;
            $BakFileSize{$incarnationNumber} = $size;
        }
    } elsif (($name =~ /^$BadLogPattern(\.bak)?$/o) && ($NaN > $1) ) {
        # A common situation we have seen is that a mksysb contains log
        # and core files from a node.  When this is installed on a new
        # node, the node_number value does not match the current node
        # number, and hagsreap does not erase these files, and they just
        # sit taking up space in /var/ha/.  Therefore, we assume the
        # first two tests will find the log & core files for THIS node,
        # we only get to these latter tests for files that follow the
        # hags pattern, but do NOT match on node number.  Try to catch
        # those here, and simply erase them.
        print "Bad log file line $.  Will erase it: $LogDir/$name, $_";
        #$cmd = "rm -f $LogDir/$name";
        #system($cmd);
        unlink( "$LogDir/$name" );
    } else {
        # print "Bad FILES line $.: $name, $_";
        next;
    }
}
close FILES; # this causes a wait on pipe child process, with $? set to its rc
&piperc( $?, $cmd, __LINE__ );

print "saveLogIncar=$saveLogIncar\n";
if( $saveLogIncar != $NaN ){
    $CoreFileName{$saveLogIncar} = $CoreSavedFileName;
}
print "CoreFileName{$saveLogIncar}=$CoreFileName{$saveLogIncar}\n";

if ("" ne $DefaultLogName) {
# get dirname of file name; strip off /s and dirname
($DefaultLogDir) = $DefaultLogName =~ /(.*)\/[^\/]+$/;
print "\$DefaultLogDir=$DefaultLogDir\n";
$cmd = "ls -lL ${DefaultLogName}* |";
# print "$cmd\n";
if ( ! open(FILES, $cmd ) ) {
	$rc = $! + 0;
	warn "\n$!\n";
}
while(<FILES>) {
    ($perms, $links, $owner, $group, $size, $month, $day, $time, $name) = split(' ');
    if ( ($name =~ /^$DefaultLogName.${Node_Number}_([0-9]+)$/o) && ($NaN > $1) ) {
    	$incarnationNumber = $1;
        # print "$name, $incarnationNumber\n";
        $name =~ s/.*\///o; # get basename of file name; strip off /s and dirname
        $DefLFileName{$incarnationNumber} = $name;
        $DefLFileSize{$incarnationNumber} = $size;
    } elsif ( $name ne $DefaultLogName ) {
        print "Bad FILE line $.  Will erase it: $name, $_";
        #$cmd = "rm -f $name";
        #system($cmd);
        unlink( "$name" );
        next;
    }
}
close FILES; # this causes a wait on pipe child process, with $? set to its rc
&piperc( $?, $cmd, __LINE__ );
}

# foreach $incar (keys(%CoreFileName)) {
#	print "$CoreFileName{$incar}, $incar, $CoreFileSize{$incar}\n";
# }

# There are three things to potentially save for each incarnation number:
# 1. the log
# 2. the core file
# 3. the log.bak file, or backup log, if it has wrapped
# The order listed in the priority order

# Our approach:
# 1. Establish the weight or priority of each incarnation number.
#    The weight of each incarnation number is it's position in the
#    sorted list, except that the priority of incarnation numbers
#    with core files is raised.
# 2. Sort the incarnation numbers by weight.
# 3. Save the files from the top incarnation numbers that fit under the SizeLimit.

# Save an incarnation number and it's weight for subsequent sorting by weight
sub AddRecord {
	local ($incar) = $_[0];
	local ($weight) = $_[1];
	local ($record) = "$weight $incar";
#	printf "AddRecord: %9.2f %10d\n", split(' ',$record);
	push(@Files, $record);
}

# taken from SequenceNum.[Ch].

$NaN = 65535;  # Not a Number
$kHalfSequence = 32767;

# note we've insured that all Incarnation Numbers are < $NaN

sub ByIncarnationNumber {
	if ($a == $b) { return 0; } # equal
#	if ($NaN == $a) { return 1; } # a > b
#	if ($NaN == $b) { return -1; } # a < b
	local($c) = ($a + $NaN -1 - $b) % $NaN;
	return ($kHalfSequence > $c) ? -1 : 1;
}

# Establish incarnation number weights by sorting by incarnation number,
# and adjusting the weights of those with core files.

$factor = 65537./137623.; # two primes, $factor =~ 1/2.1. 65537 = smallest prime > $NaN
# I do this so that no two incarnation numbers should ever have the same weight
$NumIncars = 0; # number of incarnation numbers seen
$LastIncarSeen = $NaN; # beyond the valid range
$LargestIncar = 0;
$LargestCoreIncar = 0;
if( $Daemon_Name=~/glsm/ ){
    $NumIncarToKeep = 2;
} else {
    $NumIncarToKeep = 3;
}

foreach $incarnationNumber (sort ByIncarnationNumber (keys(%CoreFileName), keys(%LogFileName), keys(%BakFileName), keys(%LongLogFileName), keys(%LongBakFileName), keys(%DefLFileName))) {
	if ($LastIncarSeen == $incarnationNumber) {
		next; # skip duplicate incarnation numbers
	}
        if( $LargestIncar < $incarnationNumber ){
            $LargestIncar = $incarnationNumber;
        }
	# new incarnation number
	$LastIncarSeen = $incarnationNumber;
	$weight = $NumIncars++; # it's sorted order
        if( $NumIncars <= $NumIncarToKeep ){
            $weight *= $factor;
        }
        if( $numberOfSaveIncar == 1){
       	    if ( defined($CoreFileName{$incarnationNumber}) ) {
		# this incar has a core file; adjust weight
		$weight *= $factor;
	    }
        } elsif ( $numberOfSaveIncar == 3 ){
            if($incarnationNumber == $saveLogIncar-1 ||
               $incarnationNumber == $saveLogIncar ||
               $incarnationNumber == $saveLogIncar+1 ){
		   $weight *= $factor;
            } 
        }
	&AddRecord( $incarnationNumber, $weight );
}
print "largest incar = $LargestIncar\n";

# foreach $record (@Files) {
	# printf "%9.2f %10d %-s\n", split(' ',$record);
# }

# OK, now that we have the weights, sort by it!

sub ByWeight {
	local($aweight, $arest, $bweight, $brest);
	($aweight, $arest) = split(' ', $a);
	($bweight, $brest) = split(' ', $b);
	$aweight <=> $bweight;
}

print "SizeLimit = $SizeLimit\n";

$CumSize = 0;
$NumTrash = 0;
$NumKeep = 0;
$n = 0;

# If we are under SizeLimit and the next incarnation number has a core file,
# keep the core file and both logs, even if we exceed SizeLimit.
# Otherwise, only keep a log if doing so will keep us under SizeLimit.

print "numberOfSaveIncar=$numberOfSaveIncar\n";

sub ProcessFile {
	local ($name, $size, $HasaCoreFile, $dir, $incarNum) = @_;

	$n++;
	if ( 0 == $NumTrash ) {
            if( $LargestIncar - $incarNum >= $NumIncarToKeep){
                if( HasaCoreFile ){
                    if($numberOfSaveIncar == 1 ){
                        if($incarNum != $saveLogIncar){
			    $NumTrash = 1;
		            $BytesKept = $CumSize;
                        }
                    } elsif($numberOfSaveIncar == 3){
                        if($incarNum != $saveLogIncar-1 &&
                           $incarNum != $saveLogIncar &&
                           $incarNum != $saveLogIncar+1 ){
			    $NumTrash = 1;
			    $BytesKept = $CumSize;
                        }
                    } else {
			$NumTrash = 1;
			$BytesKept = $CumSize;
                    }
                }#end if hascorefile
            } else {
			$NumKeep++;
            }#end of if incardiff>numtokeep
	} else {
                    if($numberOfSaveIncar == 1 ){
                        if($incarNum != $saveLogIncar){
			    $NumTrash = 1;
		            $BytesKept = $CumSize;
                        }
                    } elsif($numberOfSaveIncar == 3){
                        if($incarNum != $saveLogIncar-1 &&
                           $incarNum != $saveLogIncar &&
                           $incarNum != $saveLogIncar+1 ){
			    $NumTrash = 1;
			    $BytesKept = $CumSize;
                        }
                    } else {
			$NumTrash = 1;
			$BytesKept = $CumSize;
                    }
	}
	$CumSize += $size;
	printf "%5d %10d %8.3f %9.2f %10d %-s\n", $n, $CumSize, $CumSize/1024./1024., $weight, $size, $name;
	if ( 0 < $NumTrash ) { # trash 'em!
		# I could save all names and issue one command
		# The one command line may exceed shell limits
		# So, do'em one at a time; shouldn't be a big deal.
		#$cmd = "rm -f  $dir/$name";
		print "unlink $dir/$name\n";
		if (1 == $EraseTheFiles) {
                        unlink "$dir/$name";
		}
               
	}
}

printf "%5-s %10-s %8-s %9-s %10-s %-s\n", "n", "CumSize", "MB", "weight", "size", "name";

# Sort the incarnation numbers ordered by weight (priority), and
# keep those that fit under SizeLimit, and erase (trash) the rest,
# depending on the Erase|Print parameter.

foreach $record (sort ByWeight @Files) {
	($weight, $incar) = split(' ',$record);
	local($HasaCoreFile) = defined($CoreFileName{$incar});
	if (defined($LogFileName{$incar}) ) {
		&ProcessFile( $LogFileName{$incar}, $LogFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($LongLogFileName{$incar}) ) {
		&ProcessFile( $LongLogFileName{$incar}, $LogFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($BakFileName{$incar}) ) {
		&ProcessFile( $BakFileName{$incar}, $BakFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($LongBakFileName{$incar}) ) {
		&ProcessFile( $LongBakFileName{$incar}, $BakFileSize{$incar}, $HasaCoreFile, $LogDir, $incar );
	}
	if (defined($DefLFileName{$incar}) ) {
		&ProcessFile( $DefLFileName{$incar}, $DefLFileSize{$incar}, $HasaCoreFile, $DefaultLogDir, $incar );
	}
}

# Print out some stats on what we did

if (0 == $NumTrash) {
	$BytesKept = $CumSize;
}
$BytesTrashed = $CumSize - $BytesKept;
$MBtrashed = $BytesTrashed/1024./1024.;
printf "Keep %d logs, %d bytes, %.3f MB; Trash %d logs, %d bytes, %.3f MB.\n", $NumKeep, $BytesKept, $BytesKept/1024./1024., $NumTrash, $BytesTrashed, $MBtrashed;

$tlogs = $NumKeep + $NumTrash;
$tbytes = $BytesKept + $BytesTrashed;
printf "%d total logs, %d total bytes, %.3f total MB.\n", $tlogs, $tbytes, $tbytes/1024./1024.;

# $rc = int($NumTrash*256 + $MBtrashed);
# $rc = int($NumKeep*256*256 + $NumTrash*256 + $MBtrashed + .5);
# $rc = int($NumKeep*256*256 + $NumTrash*256 + $MBtrashed + .5);
$rc = $NumTrash;

printf "keep %d, trash %d, %d MB.  rc = %d, 0x%x\n", $NumKeep, $NumTrash, ($MBtrashed + .5), $rc, $rc;

exit $rc;
